#ifndef FEEBLE_MUNDANE_GAME_H
#define FEEBLE_MUNDANE_GAME_H

#include <optional>
#include <variant>
#include <utility>
#include <tuple>
#include <vector>
#include <array>
#include <cstddef>
#include <random>

#include "simple/support/random.hpp"
#include "simple/support/range.hpp"
#include "simple/geom.hpp"

// FIXME: consider every integral here, and replace with enum class where necessary

namespace game
{
	using unsigned2 = simple::geom::vector<unsigned,2>;
	using simple::support::range;
	namespace random = simple::support::random;

	enum class guy_type : unsigned {};
	enum class faction_id : unsigned {};
	enum class color : unsigned {};

	enum class resource_type : unsigned {};

	struct guy_variables
	{
		int health;
		int power;
		unsigned windup;
	};

	struct guy_attributes
	{
		const faction_id faction;
		const unsigned2 size;
		// indexed by resource_type
		const std::vector<unsigned> cost;
		const guy_variables defaults;

		// special powas
	};

	struct guy
	{
		guy_type type;
		enum color color;
		guy_variables vars;

		// for large guys only the top left corner cell on the field is representative, the rest are filled with defaults with reference set to the memory offset back where to find the top-left
		unsigned reference = 0;

		// matching small guys activates the front one and increases count(3)
		// combining small or large active guys, further increases count
		// count consistent with that in force and in world in general, it's not just some random number, gotta make intuitive sense
		int count = 1;
	};

	enum class outcome
	{
		wall_match, // guys who matched and built a wall, usully come back, unless special (undead make walls out of themselves?)
		stack_match, // guys who stacked to activate a bigger guy, usually come back unless special (demons eat their own to activate?)
		death, // guys who dies in battle, usually don't come back unless special (undead again, come back weakened?)
		breakthrough, // guys who broke through enemy lines and dealth morale damage, usually come back strenghtened, unless special (goblin sapper)
		retreat, // guys who didn't have enough damage to break through but didn't die either, come back as is, can't think of anything special
		remove // guys removed by player action, usually come back, unless special (sacrifice!)
	};

	struct spell
	{
	};

	struct item
	{
	};

	struct artefact
	{
	};

	enum class coward_type {};

	struct coward
	{
		coward_type type;
		faction_id faction;
		// added to the force, can be negative
		int coordination;
		// added to the force, can be negative
		int morale;

		// for casting spells
		int mana;

		std::vector<spell> spells; // cost mana
		std::vector<item> items; // cost nothing, consumable
		std::vector<artefact> artefacts; // passive
	};


	struct force
	{

		// TODO: just want a type safe integral alias with no implicit conversion, too much to ask innit
		enum class counter: unsigned {};
		friend constexpr counter& operator--(counter& c)
		{
			auto u = static_cast<std::underlying_type_t<counter>>(c);
			assert(u != 0);
			c = counter(u - 1);
			return c;
		}
		friend constexpr counter& operator++(counter& c)
		{
			using U = std::underlying_type_t<counter>;
			auto u = static_cast<U>(c);
			assert(u != std::numeric_limits<U>::max());
			c = counter(u + 1);
			return c;
		}

		// indexed by guy_type
		// how many of which type of guy, and their color
		// limitations:
		// different type guys of same cell size must have different colors
		std::vector<std::pair<counter, color>> guys;

		std::optional<coward> leader = std::nullopt;

		// maximum guys on the field
		int coordination; // = base_coordination(guys) + leader ? leader->coordination : 0;

		// tis be kinda like life, when enemy breaks your rank it's damaged,
		// once depleted you scatter
		// you can also loose by running out of guys of any color or if can't make a match
		// no infinite tier 1 guys
		int morale; // base_morale(guys) + leader ? leader->morale : 0;

		unsigned actions = 3;
	};

	using field_t = std::vector<std::optional<guy>>;

	struct battle
	{
		unsigned2 field_size;
		// two opposing fields/grids
		std::array<field_t,2> fields;
		// two opposing forces
		std::array<force, 2> forces;
		unsigned turn = 0;
	};

	extern const unsigned version;

	struct reinforce_params {};
	struct move_params {};
	struct remove_params {};
	struct end_turn_params {};

	struct guy_update {};

	struct force_count_update
	{
		unsigned force_index; // in battle
		unsigned guy_index; // in force
		force::counter count; // new value
	};

	using action = std::variant<reinforce_params, move_params, remove_params, end_turn_params>;
	using state_update = std::variant<guy_update, force_count_update>;

	struct world
	{
		// indexed by guy type
		std::vector<guy_attributes> guy_config;

		std::vector<force> forces = {};
		std::optional<battle> current_battle = {};

		std::vector<state_update> updates = {};

		random::engine::tiny<unsigned> rng{std::random_device{}};

		unsigned turn;

		field_t::iterator spawn();
		bool valid_field_position(field_t::iterator);
		void recover(unsigned force, const guy&, outcome);

		void reinforce();
		void move(unsigned from, unsigned to);
		void remove(unsigned2 at);
		void end_turn();

		void perform(action);
	};

} // namespace game

#endif /* end of include guard */
